/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2022. All rights reserved.
 * Description: 
 * Author: g00421808
 * Create: 9/16/2022
 * Notes: 
 */

#ifndef JSBIND_NAPI_VALUE_TRAIT_H
#define JSBIND_NAPI_VALUE_TRAIT_H

#include <js_native_api.h>
#include <node_api.h>

#include "jsbind/value/napi/napi_value.h"
#include "jsbind/logging/logging.h"

namespace Jsb {

template<typename T>
class NapiValueMaker : public NapiValue<typename DetectPolicies<T>::Type> {
public:
    using NapiValue<typename DetectPolicies<T>::Type>::NapiValue;

    ~NapiValueMaker()
    {
    }
};

/**
 * @brief: NapiValueTrait::Cast 使用模板类函数，因为模板函数无法自动推导返回值
 */
template<typename T>
struct NapiValueTrait {
    static T Cast(NapiValue<T>& value)
    {
        return static_cast<T&>(value);
    }

    static T Cast(NapiValue<Equivalence<T>>& value)
    {
        return static_cast<T&>(value);
    }
};

template<typename T>
struct NapiValueTrait<T*> {
    static T* Cast(NapiValue<T*>& value) {
        return static_cast<T*>(value);
    }
};

template<>
struct NapiValueTrait<bool> {
    static bool Cast(NapiValue<bool>& value) {
        return static_cast<bool>(value);
    }
};

template<>
struct NapiValueTrait<int> {
    static int Cast(NapiValue<int>& value) {
        return static_cast<int>(value);
    }
};

template<>
struct NapiValueTrait<int64_t> {
    static int64_t Cast(NapiValue<int64_t>& value) {
        return static_cast<int64_t>(value);
    }
};

template<>
struct NapiValueTrait<double> {
    static double Cast(NapiValue<double>& value) {
        return static_cast<double>(value);
    }
};

template<>
struct NapiValueTrait<float> {
    static float Cast(NapiValue<float>& value) {
        return static_cast<float>(value);
    }
};

template<>
struct NapiValueTrait<std::string> {
    static std::string Cast(NapiValue<std::string>& value) {
        return value.operator std::string();
    }
};

template<typename T>
struct NapiValueTrait<std::shared_ptr<T>> {
    static std::shared_ptr<T> Cast(NapiValue<std::shared_ptr<T>>& value) {
        return value.GetShared();
    }
};

template<typename R, typename... P>
struct NapiValueTrait<std::function<R (P...)>> {
    static std::function<R (P...)> Cast(NapiValue<std::function<R (P...)>>& value) {
        return value.GetFunction();
    }
};

template<typename R, typename... P>
struct NapiValueTrait<Callback<R (P...)>> {
    static Callback<R (P...)> Cast(const NapiValue<Callback<R (P...)>> &value)
    {
        return value.GetFunction();
    }
};

template<typename R, typename... P>
struct NapiValueTrait<SafetyCallback<R (P...)>> {
    static SafetyCallback<R (P...)> Cast(const NapiValue<SafetyCallback<R (P...)>> &value)
    {
        return value.GetFunction();
    }
};

template<>
struct NapiValueTrait<ArrayBuffer> {
    static ArrayBuffer Cast(NapiValue<ArrayBuffer> &value)
    {
        return value.GetArrayBuffer();
    }
};

template<typename T>
struct NapiValueTrait<std::vector<T>> {
    static std::vector<T> Cast(NapiValue<std::vector<T>> &value)
    {
        return value.GetVector();
    }
};

template<typename T, std::size_t N>
struct NapiValueTrait<std::array<T, N>> {
    static std::array<T, N> Cast(NapiValue<std::array<T, N>> &value)
    {
        return value.GetArray();
    }
};

template<>
struct NapiValueTrait<JSFunction> {
    static JSFunction Cast(NapiValue<JSFunction>& value) {
        return value.GetJSFunction();
    }
};


// NapiValue<std::vector<T>>
template<typename T>
std::vector<T> NapiValue<std::vector<T>>::GetVector() {
    napi_status status;
    napi_env env = env_;
    std::vector<T> result;
    uint32_t length;
    status = napi_get_array_length(env, value_, &length);
    JSB_DCHECK(status == napi_ok);
    for (uint32_t i = 0; i < length; i++) {
        napi_value element;
        status = napi_get_element(env, value_, i, &element);
        JSB_DCHECK(status == napi_ok);
        auto napiValue = NapiValue<typename ValueDefiner<T>::RawType>(env, element);
        T t = NapiValueTrait<typename ValueDefiner<T>::RawType>::Cast(napiValue);
        result.push_back(t);
    }

    return result;
}
// std::array
template<typename T, size_t N>
std::array<T, N> NapiValue<std::array<T, N>>::GetArray() {
    napi_status status;
    napi_env env = env_;
    std::array<T, N> result;
    uint32_t length;
    status = napi_get_array_length(env, value_, &length);
    JSB_DCHECK(status == napi_ok);
    for (uint32_t i = 0; i < length; i++) {
        napi_value element;
        status = napi_get_element(env, value_, i, &element);
        JSB_DCHECK(status == napi_ok);
        auto napiValue = NapiValueMaker<T>(env, element);
        T t = ValueTrait<typename ValueDefiner<T>::RawType>::Cast(napiValue);
        result[i] = std::move(t);
    }

    return result;
}

template<typename R, typename... P>
R Callback<R (P...)>::Call(P&&... args) const {
    napi_status status;
    napi_env env = env_;
    napi_value cb = cb_;
    size_t argc = sizeof...(P);
    std::array<napi_value, sizeof...(P)> argv = {NapiValue<typename ValueDefiner<P>::RawType>::ToNapiValue(env, std::forward<P>(args))...};

    napi_value undefined;
    status = napi_get_undefined(env, &undefined);
    JSB_DCHECK(status == napi_ok) << "status: " << status;

    napi_value result;
    status = napi_call_function(env,
                                undefined,
                                cb,
                                argc,
                                argv.data(),
                                &result);
    JSB_DCHECK(status == napi_ok) << "status: " << status;

    if constexpr (std::is_void<R>::value) {
        return;
    } else {
        JSB_CHECK(NapiValue<R>::CheckType(env, result)) << "should return type with: " << NapiValue<R>::ExpectedType();
        auto napiValue = NapiValueMaker<R>(env, result);
        return NapiValueTrait<R>::Cast(napiValue);
    }
}

template<typename R, typename... P>
R Callback<R (P...)>::CallMethod(napi_env env, napi_value target, P ... args) const
{
    size_t argc = sizeof...(P);
    std::array<napi_value, sizeof...(P)> argv = {NapiValue<P>::ToNapiValue(env, args)...};

    napi_value result;
    napi_status status;
    status = napi_call_function(env,
                                target,
                                cb_,
                                argc,
                                argv.data(),
                                &result);
    JSB_DCHECK(status == napi_ok);
}

template<typename R, typename... P>
template<size_t... Index>
napi_value NapiCallbackBinder<R, P...>::SafetyWrapper(napi_env& env, napi_callback_info info, std::index_sequence<Index...>) {
        JSB_DLOG(DEBUG) << "NapiCallbackBinder::SafetyWrapper";
        napi_status status;
        void* invoker = nullptr;
        size_t argc = sizeof...(Index);
        std::array<napi_value, sizeof...(Index)> args;

        status = napi_get_cb_info(env, info, &argc, args.data(), nullptr, &invoker);
        JSB_DCHECK(status == napi_ok);
        JSB_DCHECK(invoker != nullptr);
        auto tuple = std::make_tuple(NapiValueMaker<typename ValueDefiner<P>::RawType>(env, args[Index])...);
        auto valueTuple = std::make_tuple(NapiValueTrait<typename ValueDefiner<P>::RawType>::Cast(std::get<Index>(tuple))...);

        return WrapperForward(env, invoker, std::get<Index>(valueTuple)...);
    }
    
    

    
template<typename R, typename... P>
template<typename... Args>
napi_value NapiCallbackBinder<R, P...>::WrapperForward(napi_env& env, void* invoker, Args&&... args) {
        JSB_DLOG(DEBUG) << "NapiCallbackBinder::WrapperForward";

        if constexpr (std::is_void<R>::value) {
            ParentType::InnerWrapper(reinterpret_cast<std::function<R (P...)>*>(invoker), std::forward<Args>(args)...);
            return nullptr;
        } else {
            R r = ParentType::InnerWrapper(reinterpret_cast<std::function<R (P...)>*>(invoker), std::forward<Args>(args)...);
            return NapiValue<R>::ToNapiValue(env, ValueTrait<R>::Cast(r));
        }
    }
} // namespace Jsb
#endif //JSBIND_NAPI_VALUE_TRAIT_H
